use asset_service::db;
use dotenvy::dotenv;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use serial_test::serial;
use sqlx::MySqlPool;
use sqlx::{FromRow, Row};
use std::error::Error;
use proto_lib::common;

macro_rules! debug_query_as {
    ($sql:expr, $($param:expr),*) => {{
        let sql = $sql;
        let params = vec![$(format!("{:?}", $param)),*];
        info!("SQL: {} with params: {:?}", sql, params);

        let mut query = sqlx::query_as::<_, User>(sql);
        $(
            query = query.bind($param);
        )*
        query
    }};
}

#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct User {
    pub id: i64,
    pub name: String,
    pub email: Option<String>,
    pub age: i8,
}

// pub trait DecimalExtension {
//     fn zero() -> common::Decimal {
//         common::Decimal {
//             value: "0".to_string(),
//         }
//     }
// }
//
// impl DecimalExtension for Decimal {
// }

#[cfg(test)]
mod tests {
    use super::*;
    use asset_service::db;
    use asset_service::db::Database;
    use asset_service::vo::Balance;
    use common_lib::BalanceType;
    use log::{info, LevelFilter};
    use proto_lib::{asset, common, AssetBalance, QueryResponse};
    use sqlx::encode::IsNull::No;
    use sqlx::{Execute, MySql};
    use std::collections::HashMap;
    use std::ops::Add;
    use std::str::FromStr;
    use rust_decimal::prelude::Zero;
    use BalanceType::*;

    #[tokio::test]
    #[serial]
    async fn test_create_balance() {
        dotenv().ok();
        let db = Database::new().await.unwrap();

        let balance = Balance {
            user_id: 672,
            asset: 2,
            t: BalanceType::Available,
            value: Decimal::new(1000, 2),
        };

        let record = db.create_balance(balance).await;
        println!("Created record: {:?}", record);
    }

    async fn test_db_init() -> Database {
        dotenv().ok();
        env_logger::Builder::new()
            .filter_level(LevelFilter::Info)
            .init();
        let db = Database::new().await.unwrap();
        db
    }
    #[tokio::test]
    #[serial]
    async fn test_get_balance() {
        let db = test_db_init().await;
        // let rows = db.get_user_balance_list(671,Some(&vec![1,2])).await.unwrap();
        let row = db
            .get_user_balance(671, 2, BalanceType::Available)
            .await
            .unwrap();
        info!("row:{:?}", row);
        // if let Some(first) = rows.first() {
        //     for row in rows.iter() {
        //         print!("{:?}", row);
        //     }
        //     // assert_eq!(rows.expect("REASON").len(), 1);
        // }
    }

    #[tokio::test]
    #[serial]
    async fn get_user_balance_list() {
        let db = test_db_init().await;

        // let ids = [671,672];
        // let placeholders = vec!["?"; ids.len()].join(",");
        // let sql = format!("SELECT * FROM balance WHERE user_id IN ({})", placeholders);
        // for id in ids {
        //     query = query.bind(id);
        // }
        // let query = sqlx::query_as::<_, db::Balance>("SELECT * FROM balance WHERE user_id = ?")
        //     .bind(671);

        let ids = [1, 2];
        let ids_str = ids
            .iter()
            .map(|id| id.to_string())
            .collect::<Vec<_>>()
            .join(",");

        let user_id = 671;
        let sql = format!(
            "SELECT * FROM balance WHERE user_id=? and asset IN ({})",
            ids_str
        );

        let mut query = sqlx::query_as::<_, db::Balance>(&sql);
        query = query.bind(&user_id);

        let sql = query.sql();
        info!("SQL: {}", sql);
        let result = query.fetch_all(&db.pool).await.unwrap();
        info!("result:{:?}", result);
    }

    #[tokio::test]
    #[serial]
    async fn test_update_balance() {
        let db = test_db_init().await;
        db.update_balance(
            671,
            2,
            BalanceType::Available,
            Decimal::try_from(1000.00).unwrap(),
            0,
        )
        .await
        .unwrap();
    }

    fn update_asset_balance(item: &mut AssetBalance, row: &db::Balance) {
        match BalanceType::from_code(row.t).unwrap() {
            Available => {
                item.balance.as_mut().unwrap().available = Some(common::Decimal {
                    value: row.value.to_string(),
                });
            }
            FrozenFee | FrozenMargin => {
                let v: common::Decimal = item.balance.clone().unwrap().frozen.unwrap_or(common::Decimal { value: "0".to_string() });
                // let v: common::Decimal = item.balance.clone().unwrap().frozen.unwrap_or(common::Decimal::zero());
                item.balance.as_mut().unwrap().frozen = Some(common::Decimal {
                    value: row.value.add(Decimal::from_str(&*v.value).unwrap()).to_string(),
                });
            }
            _ => {}
        }
    }

    fn new_asset_balance(row: &db::Balance) -> AssetBalance {
        let mut r = AssetBalance {
            asset: row.asset as u32,
            balance: Some(asset::Balance {
                available: None,
                frozen: None,
            }),
        };

        match BalanceType::from_code(row.t).unwrap() {
            Available => {
                r.balance.as_mut().unwrap().available = Some(common::Decimal {
                    value: row.value.to_string(),
                });
            }
            FrozenFee | FrozenMargin => {
                r.balance.as_mut().unwrap().frozen = Some(common::Decimal {
                    value: row.value.to_string(),
                });
            }
            _ => {}
        }
        r
    }
    #[tokio::test]
    #[serial]
    async fn test_get_balance_list_to_resp() {
        //-> Vec<AssetBalance> {
        let db = test_db_init().await;
        let rows = db
            .get_user_balance_list(671, Some(&*vec![1, 2]))
            .await
            .unwrap();
        // let mut resp = QueryResponse{ balances: vec![] };

        let mut t: HashMap<u16, AssetBalance> = HashMap::new();

        // let a = asset::Balance {
        //     available: None,
        //     frozen: None,
        // };
        //
        // let b = common::Decimal {
        //     value: "".to_string(),
        // };
        //
        // let mut c = AssetBalance {
        //     asset: 0,
        //     balance: None,
        // };
        // c.balance = Some(asset::Balance {
        //     available: None,
        //     frozen: None,
        // });
        // c.balance.as_mut().unwrap().available = Some(b);

        // let mut a = t.get_mut(&1);//.borrow_mut();
        // // let mut b = AssetBalance { asset: 0, balance: None };//a.unwrap();
        // let b = a.unwrap();//.borrow_mut();
        // b = AssetBalance {
        //     asset: 0,
        //     balance: None,
        // };
        // b.balance = None;
        // b = &AssetBalance { asset: 0, balance: None };
        // b.balance.unwrap().available = None;
        for row in rows {
            // let r = Balance::new(row.user_id, row.asset,BalanceType::from_code(row.t).into(), row.value);
            match t.get_mut(&row.asset) {
                Some(balance) => {
                    update_asset_balance(balance, &row);
                }
                None => {
                    t.insert(row.asset.clone(), new_asset_balance(&row));
                }
            }
        }

        let result:Vec<AssetBalance> = t.into_values().collect();
        info!("result:{:?}", result);
    }
    async fn test_query_as(pool: &MySqlPool) -> Result<(), sqlx::Error> {
        // 方式 1: 使用 query
        let query = sqlx::query::<MySql>("SELECT * FROM balance WHERE id = ?").bind(1);

        // 方式 2: 使用 query_as
        let query =
            sqlx::query_as::<MySql, db::Balance>("SELECT * FROM balance WHERE id = ?").bind(1);

        // 方式 3: 使用类型推导
        // let query = query!("SELECT * FROM users balance id = ?", 1);

        // 方式 4: 使用 query_as! 宏
        // let query = query_as!(db::Balance, "SELECT * FROM balance WHERE id = ?", 1);

        Ok(())
    }

    //     async fn log_query<T>(query: sqlx::query<MySql>) -> Result<(), sqlx::Error> {
    //         // 获取查询的描述信息
    //         let sql = query.sql();
    //         let describe = query.describe().await?;
    //
    //         info!("SQL: {}", sql);
    //         info!("Parameters: {:?}", describe.parameters());
    //
    //     //     info!(sql = %query_str,
    //     // params = ?params,
    //     // duration_ms = %duration.as_millis(),
    //     // "query executed"
    // // );
    //
    //         Ok(())
    //     }

    async fn find_users(
        pool: &MySqlPool,
        min_age: i32,
        name: &str,
    ) -> Result<Vec<User>, sqlx::Error> {
        debug_query_as!(
            "SELECT * FROM users WHERE age > ? AND name LIKE ?",
            min_age,
            format!("%{}%", name)
        )
        .fetch_all(pool)
        .await
    }
    #[tokio::test]
    #[serial]
    async fn test_find_user() {
        dotenv().ok();
        env_logger::Builder::new()
            .filter_level(LevelFilter::Info)
            .init();
        let db = Database::new().await.unwrap();

        let result = find_users(&db.pool, 10, "a").await.unwrap();
        info!("result:{:?}", result);
    }
}
